
/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache POI" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache POI", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.poi.hssf.model;

import org.apache.poi.hssf.record.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.hssf.util.SheetReferences;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

/**
 * Low level model implementation of a Workbook.  Provides creational methods
 * for settings and objects contained in the workbook object.
 * <P>
 * This file contains the low level binary records starting at the workbook's BOF and
 * ending with the workbook's EOF.  Use HSSFWorkbook for a high level representation.
 * <P>
 * The structures of the highlevel API use references to this to perform most of their
 * operations.  Its probably unwise to use these low level structures directly unless you
 * really know what you're doing.  I recommend you read the Microsoft Excel 97 Developer's
 * Kit (Microsoft Press) and the documentation at http://sc.openoffice.org/excelfileformat.pdf
 * before even attempting to use this.
 *
 *
 * @author  Shawn Laubach (slaubach at apache dot org) (Data Formats)
 * @author  Andrew C. Oliver (acoliver at apache dot org)
 * @author  Glen Stampoultzis (glens at apache.org)
 * @author  Sergei Kozello (sergeikozello at mail.ru)
 * @author  Luc Girardin (luc dot girardin at macrofocus dot com)
 * @author  Dan Sherman (dsherman at isisph.com)
 * @author  Brian Sanders (bsanders at risklabs dot com) - custom palette
 * @see org.apache.poi.hssf.usermodel.HSSFWorkbook
 * @version 1.0-pre
 */

public class Workbook implements Model {
    private static final int   DEBUG       = POILogger.DEBUG;

//    public static Workbook currentBook = null;

    /**
     * constant used to set the "codepage" wherever "codepage" is set in records
     * (which is duplciated in more than one record)
     */

    private final static short CODEPAGE    = ( short ) 0x4b0;

    /**
     * this contains the Worksheet record objects
     */
    protected WorkbookRecordList        records     = new WorkbookRecordList();

    /**
     * this contains a reference to the SSTRecord so that new stings can be added
     * to it.
     */
    protected SSTRecord        sst         = null;

    /**
     * Holds the Extern Sheet with references to bound sheets
     */
    protected ExternSheetRecord externSheet= null;

    /**
     * holds the "boundsheet" records (aka bundlesheet) so that they can have their
     * reference to their "BOF" marker
     */
    protected ArrayList        boundsheets = new ArrayList();

    protected ArrayList        formats = new ArrayList();

    protected ArrayList        names = new ArrayList();

    protected int              numxfs      = 0;   // hold the number of extended format records
    protected int              numfonts    = 0;   // hold the number of font records
    private short              maxformatid  = -1;  // holds the max format id
    private boolean            uses1904datewindowing  = false;  // whether 1904 date windowing is being used

    private static POILogger   log = POILogFactory.getLogger(Workbook.class);

    /**
     * Creates new Workbook with no intitialization --useless right now
     * @see #createWorkbook(List)
     */
    public Workbook() {
    }

    /**
     * read support  for low level
     * API.  Pass in an array of Record objects, A Workbook
     * object is constructed and passed back with all of its initialization set
     * to the passed in records and references to those records held. Unlike Sheet
     * workbook does not use an offset (its assumed to be 0) since its first in a file.
     * If you need an offset then construct a new array with a 0 offset or write your
     * own ;-p.
     *
     * @param recs an array of Record objects
     * @return Workbook object
     */
    public static Workbook createWorkbook(List recs) {
        log.log(DEBUG, "Workbook (readfile) created with reclen=",
        new Integer(recs.size()));
        Workbook  retval  = new Workbook();
        ArrayList records = new ArrayList(recs.size() / 3);

        for (int k = 0; k < recs.size(); k++) {
            Record rec = ( Record ) recs.get(k);

            if (rec.getSid() == EOFRecord.sid) {
                records.add(rec);
                log.log(DEBUG, "found workbook eof record at " + k);
                break;
            }
            switch (rec.getSid()) {

                case BoundSheetRecord.sid :
                    log.log(DEBUG, "found boundsheet record at " + k);
                    retval.boundsheets.add(rec);
                    retval.records.setBspos( k );
                    break;

                case SSTRecord.sid :
                    log.log(DEBUG, "found sst record at " + k);
                    retval.sst = ( SSTRecord ) rec;
                    break;

                case FontRecord.sid :
                    log.log(DEBUG, "found font record at " + k);
                    retval.records.setFontpos( k );
                    retval.numfonts++;
                    break;

                case ExtendedFormatRecord.sid :
                    log.log(DEBUG, "found XF record at " + k);
                    retval.records.setXfpos( k );
                    retval.numxfs++;
                    break;

                case TabIdRecord.sid :
                    log.log(DEBUG, "found tabid record at " + k);
                    retval.records.setTabpos( k );
                    break;

                case ProtectRecord.sid :
                    log.log(DEBUG, "found protect record at " + k);
                    retval.records.setProtpos( k );
                    break;

                case BackupRecord.sid :
                    log.log(DEBUG, "found backup record at " + k);
                    retval.records.setBackuppos( k );
                    break;
                case ExternSheetRecord.sid :
                    log.log(DEBUG, "found extern sheet record at " + k);
                    retval.externSheet = ( ExternSheetRecord ) rec;
                    break;
                case NameRecord.sid :
                    log.log(DEBUG, "found name record at " + k);
                    retval.names.add(rec);
//                    retval.records.namepos = k;
                    break;
                case SupBookRecord.sid :
                    log.log(DEBUG, "found SupBook record at " + k);
//                    retval.records.supbookpos = k;
                    break;
	        case FormatRecord.sid :
                log.log(DEBUG, "found format record at " + k);
                retval.formats.add(rec);
                retval.maxformatid = retval.maxformatid >= ((FormatRecord)rec).getIndexCode() ? retval.maxformatid : ((FormatRecord)rec).getIndexCode();
		    break;
                case DateWindow1904Record.sid :
                    log.log(DEBUG, "found datewindow1904 record at " + k);
                    retval.uses1904datewindowing = ((DateWindow1904Record)rec).getWindowing() == 1;
                    break;
                case PaletteRecord.sid:
                    log.log(DEBUG, "found palette record at " + k);
                    retval.records.setPalettepos( k );
                default :
            }
            records.add(rec);
        }
        //What if we dont have any ranges and supbooks
//        if (retval.records.supbookpos == 0) {
//            retval.records.supbookpos = retval.records.bspos + 1;
//            retval.records.namepos    = retval.records.supbookpos + 1;
//        }

        retval.records.setRecords(records);
        log.log(DEBUG, "exit create workbook from existing file function");
        return retval;
    }

    /**
     * Creates an empty workbook object with three blank sheets and all the empty
     * fields.  Use this to create a workbook from scratch.
     */
    public static Workbook createWorkbook()
    {
        log.log( DEBUG, "creating new workbook from scratch" );
        Workbook retval = new Workbook();
        ArrayList records = new ArrayList( 30 );
        ArrayList formats = new ArrayList( 8 );

        records.add( retval.createBOF() );
        records.add( retval.createInterfaceHdr() );
        records.add( retval.createMMS() );
        records.add( retval.createInterfaceEnd() );
        records.add( retval.createWriteAccess() );
        records.add( retval.createCodepage() );
        records.add( retval.createDSF() );
        records.add( retval.createTabId() );
        retval.records.setTabpos( records.size() - 1 );
        records.add( retval.createFnGroupCount() );
        records.add( retval.createWindowProtect() );
        records.add( retval.createProtect() );
        retval.records.setProtpos( records.size() - 1 );
        records.add( retval.createPassword() );
        records.add( retval.createProtectionRev4() );
        records.add( retval.createPasswordRev4() );
        records.add( retval.createWindowOne() );
        records.add( retval.createBackup() );
        retval.records.setBackuppos( records.size() - 1 );
        records.add( retval.createHideObj() );
        records.add( retval.createDateWindow1904() );
        records.add( retval.createPrecision() );
        records.add( retval.createRefreshAll() );
        records.add( retval.createBookBool() );
        records.add( retval.createFont() );
        records.add( retval.createFont() );
        records.add( retval.createFont() );
        records.add( retval.createFont() );
        retval.records.setFontpos( records.size() - 1 );   // last font record postion
        retval.numfonts = 4;

        // set up format records
        for ( int i = 0; i <= 7; i++ )
        {
            Record rec;
            rec = retval.createFormat( i );
            retval.maxformatid = retval.maxformatid >= ( (FormatRecord) rec ).getIndexCode() ? retval.maxformatid : ( (FormatRecord) rec ).getIndexCode();
            formats.add( rec );
            records.add( rec );
        }
        retval.formats = formats;

        for ( int k = 0; k < 21; k++ )
        {
            records.add( retval.createExtendedFormat( k ) );
            retval.numxfs++;
        }
        retval.records.setXfpos( records.size() - 1 );
        for ( int k = 0; k < 6; k++ )
        {
            records.add( retval.createStyle( k ) );
        }
        retval.records.setPalettepos( records.size() );
        records.add( retval.createUseSelFS() );
        for ( int k = 0; k < 1; k++ )
        {   // now just do 1
            BoundSheetRecord bsr =
                    (BoundSheetRecord) retval.createBoundSheet( k );

            records.add( bsr );
            retval.boundsheets.add( bsr );
            retval.records.setBspos( records.size() - 1 );
        }
//        retval.records.supbookpos = retval.records.bspos + 1;
//        retval.records.namepos = retval.records.supbookpos + 2;
        records.add( retval.createCountry() );
        retval.sst = (SSTRecord) retval.createSST();
        records.add( retval.sst );
        records.add( retval.createExtendedSST() );

        records.add( retval.createEOF() );
        retval.records.setRecords(records);
        log.log( DEBUG, "exit create new workbook from scratch" );
        return retval;
    }


	/**Retrieves the Builtin NameRecord that matches the name and index
	 * There shouldn't be too many names to make the sequential search too slow
	 * @param name byte representation of the builtin name to match
	 * @param sheetIndex Index to match
	 * @return null if no builtin NameRecord matches
	 */
	public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
	{
	    Iterator iterator = names.iterator();
	    while (iterator.hasNext()) {
	        NameRecord record = ( NameRecord ) iterator.next();
	
	        //print areas are one based
	        if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
	            return record;
	        }
	    }
	    
	    return null;
	    
	}

	/**
	 * Removes the specified Builtin NameRecord that matches the name and index
	 * @param name byte representation of the builtin to match
	 * @param sheetIndex zero-based sheet reference
	 */
	public void removeBuiltinRecord(byte name, int sheetIndex) {
		//the name array is smaller so searching through it should be faster than
		//using the findFirstXXXX methods
		NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
		if (record != null) {
			names.remove(record);
		}
		
	}

    public int getNumRecords() {
        return records.size();
    }

    /**
     * gets the font record at the given index in the font table.  Remember
     * "There is No Four" (someone at M$ must have gone to Rocky Horror one too
     * many times)
     *
     * @param idx the index to look at (0 or greater but NOT 4)
     * @return FontRecord located at the given index
     */

    public FontRecord getFontRecordAt(int idx) {
        int index = idx;

        if (index > 4) {
            index -= 1;   // adjust for "There is no 4"
        }
        if (index > (numfonts - 1)) {
            throw new ArrayIndexOutOfBoundsException(
            "There are only " + numfonts
            + " font records, you asked for " + idx);
        }
        FontRecord retval =
        ( FontRecord ) records.get((records.getFontpos() - (numfonts - 1)) + index);

        return retval;
    }

    /**
     * creates a new font record and adds it to the "font table".  This causes the
     * boundsheets to move down one, extended formats to move down (so this function moves
     * those pointers as well)
     *
     * @return FontRecord that was just created
     */

    public FontRecord createNewFont() {
        FontRecord rec = ( FontRecord ) createFont();

        records.add(records.getFontpos()+1, rec);
        records.setFontpos( records.getFontpos() + 1 );
        numfonts++;
        return rec;
    }

    /**
     * gets the number of font records
     *
     * @return   number of font records in the "font table"
     */

    public int getNumberOfFontRecords() {
        return numfonts;
    }

    /**
     * Sets the BOF for a given sheet
     *
     * @param sheetnum the number of the sheet to set the positing of the bof for
     * @param pos the actual bof position
     */

    public void setSheetBof(int sheetnum, int pos) {
        log.log(DEBUG, "setting bof for sheetnum =", new Integer(sheetnum),
        " at pos=", new Integer(pos));
        checkSheets(sheetnum);
        (( BoundSheetRecord ) boundsheets.get(sheetnum))
        .setPositionOfBof(pos);
    }

    /**
     * Returns the position of the backup record.
     */

    public BackupRecord getBackupRecord() {
        return ( BackupRecord ) records.get(records.getBackuppos());
    }


    /**
     * sets the name for a given sheet.  If the boundsheet record doesn't exist and
     * its only one more than we have, go ahead and create it.  If its > 1 more than
     * we have, except
     *
     * @param sheetnum the sheet number (0 based)
     * @param sheetname the name for the sheet
     */

    // for compatibility
    public void setSheetName(int sheetnum, String sheetname ) {
        setSheetName( sheetnum, sheetname, (byte)0 );
    }

    public void setSheetName(int sheetnum, String sheetname, short encoding ) {
        checkSheets(sheetnum);
        BoundSheetRecord sheet = (BoundSheetRecord)boundsheets.get( sheetnum );
        sheet.setSheetname(sheetname);
        sheet.setSheetnameLength( (byte)sheetname.length() );
		sheet.setCompressedUnicodeFlag( (byte)encoding );
    }
    
    	/**
	 * sets the order of appearance for a given sheet.
	 *
	 * @param sheetname the name of the sheet to reorder
	 * @param pos the position that we want to insert the sheet into (0 based)
	 */
    
    public void setSheetOrder(String sheetname, int pos ) {
	int sheetNumber = getSheetIndex(sheetname);
	//remove the sheet that needs to be reordered and place it in the spot we want
	boundsheets.add(pos, boundsheets.remove(sheetNumber));	
    }

    /**
     * gets the name for a given sheet.
     *
     * @param sheetnum the sheet number (0 based)
     * @return sheetname the name for the sheet
     */

    public String getSheetName(int sheetnum) {
        return (( BoundSheetRecord ) boundsheets.get(sheetnum))
        .getSheetname();
    }

    /**
     * get the sheet's index
     * @param name  sheet name
     * @return sheet index or -1 if it was not found.
     */

    public int getSheetIndex(String name) {
        int retval = -1;

        for (int k = 0; k < boundsheets.size(); k++) {
            String sheet = getSheetName(k);

            if (sheet.equals(name)) {
                retval = k;
                break;
            }
        }
        return retval;
    }

    /**
     * if we're trying to address one more sheet than we have, go ahead and add it!  if we're
     * trying to address >1 more than we have throw an exception!
     */

    private void checkSheets(int sheetnum) {
        if ((boundsheets.size()) <= sheetnum) {   // if we're short one add another..
            if ((boundsheets.size() + 1) <= sheetnum) {
                throw new RuntimeException("Sheet number out of bounds!");
            }
            BoundSheetRecord bsr = (BoundSheetRecord ) createBoundSheet(sheetnum);

            records.add(records.getBspos()+1, bsr);
            records.setBspos( records.getBspos() + 1 );
            boundsheets.add(bsr);
            fixTabIdRecord();
        }
    }

    public void removeSheet(int sheetnum) {
        if (boundsheets.size() > sheetnum) {
            records.remove(records.getBspos() - (boundsheets.size() - 1) + sheetnum);
//            records.bspos--;
            boundsheets.remove(sheetnum);
            fixTabIdRecord();
        }
    }

    /**
     * make the tabid record look like the current situation.
     *
     */
    private void fixTabIdRecord() {
        TabIdRecord tir = ( TabIdRecord ) records.get(records.getTabpos());
        short[]     tia = new short[ boundsheets.size() ];

        for (short k = 0; k < tia.length; k++) {
            tia[ k ] = k;
        }
        tir.setTabIdArray(tia);
    }

    /**
     * returns the number of boundsheet objects contained in this workbook.
     *
     * @return number of BoundSheet records
     */

    public int getNumSheets() {
        log.log(DEBUG, "getNumSheets=", new Integer(boundsheets.size()));
        return boundsheets.size();
    }

    /**
     * get the number of ExtendedFormat records contained in this workbook.
     *
     * @return int count of ExtendedFormat records
     */

    public int getNumExFormats() {
        log.log(DEBUG, "getXF=", new Integer(numxfs));
        return numxfs;
    }

    /**
     * gets the ExtendedFormatRecord at the given 0-based index
     *
     * @param index of the Extended format record (0-based)
     * @return ExtendedFormatRecord at the given index
     */

    public ExtendedFormatRecord getExFormatAt(int index) {
        int xfptr = records.getXfpos() - (numxfs - 1);

        xfptr += index;
        ExtendedFormatRecord retval =
        ( ExtendedFormatRecord ) records.get(xfptr);

        return retval;
    }

    /**
     * creates a new Cell-type Extneded Format Record and adds it to the end of
     *  ExtendedFormatRecords collection
     *
     * @return ExtendedFormatRecord that was created
     */

    public ExtendedFormatRecord createCellXF() {
        ExtendedFormatRecord xf = createExtendedFormat();

        records.add(records.getXfpos()+1, xf);
        records.setXfpos( records.getXfpos() + 1 );
        numxfs++;
        return xf;
    }

    /**
     * Adds a string to the SST table and returns its index (if its a duplicate
     * just returns its index and update the counts)
     *
     * @param string the string to be added to the SSTRecord
     * @param use16bits whether to use utf 16 or false for compressed unicode
     * @return index of the string within the SSTRecord
     */

    public int addSSTString(String string, boolean use16bits) {
        log.log(DEBUG, "insert to sst string='", string, "' and use16bits= ",
        new Boolean(use16bits));
        if (sst == null) {
            insertSST();
        }
        return sst.addString(string, use16bits);
    }

    /**
     * Adds a string to the SST table and returns its index (if its a duplicate
     * just returns its index and update the counts) ASSUMES compressed unicode
     * (meaning 8bit)
     *
     * @param string the string to be added to the SSTRecord
     *
     * @return index of the string within the SSTRecord
     */

    public int addSSTString(String string) {
        return addSSTString(string, false);
    }

    /**
     * given an index into the SST table, this function returns the corresponding String value
     * @return String containing the SST String
     */

    public String getSSTString(int str) {
        if (sst == null) {
            insertSST();
        }
        String retval = sst.getString(str);

        log.log(DEBUG, "Returning SST for index=", new Integer(str),
        " String= ", retval);
        return retval;
    }

    /**
     * use this function to add a Shared String Table to an existing sheet (say
     * generated by a different java api) without an sst....
     * @see #createSST()
     * @see org.apache.poi.hssf.record.SSTRecord
     */

    public void insertSST() {
        log.log(DEBUG, "creating new SST via insertSST!");
        sst = ( SSTRecord ) createSST();
        records.add(records.size() - 1, createExtendedSST());
        records.add(records.size() - 2, sst);
    }

    /**
     * Serializes all records int the worksheet section into a big byte array. Use
     * this to write the Workbook out.
     *
     * @return byte array containing the HSSF-only portions of the POIFS file.
     */

    public byte [] serialize() {
        log.log(DEBUG, "Serializing Workbook!");
        byte[] retval    = null;

        // ArrayList bytes     = new ArrayList(records.size());
        int    arraysize = getSize();
        int    pos       = 0;

        // for (int k = 0; k < records.size(); k++)
        // {
        // bytes.add((( Record ) records.get(k)).serialize());
        //        }
        // for (int k = 0; k < bytes.size(); k++)
        // {
        // arraysize += (( byte [] ) bytes.get(k)).length;
        // }
        retval = new byte[ arraysize ];
        for (int k = 0; k < records.size(); k++) {

            // byte[] rec = (( byte [] ) bytes.get(k));
            // System.arraycopy(rec, 0, retval, pos, rec.length);
            Record record = records.get(k);
            // Let's skip RECALCID records, as they are only use for optimization
	    if(record.getSid() != RecalcIdRecord.sid || ((RecalcIdRecord)record).isNeeded()) {
                pos += record.serialize(pos, retval);   // rec.length;
	    }
        }
        log.log(DEBUG, "Exiting serialize workbook");
        return retval;
    }

    /**
     * Serializes all records int the worksheet section into a big byte array. Use
     * this to write the Workbook out.
     * @param offset of the data to be written
     * @param data array of bytes to write this to
     */

    public int serialize(int offset, byte [] data) {
        log.log(DEBUG, "Serializing Workbook with offsets");

        // ArrayList bytes     = new ArrayList(records.size());
        //        int arraysize = getSize();   // 0;
        int pos       = 0;

        //        for (int k = 0; k < records.size(); k++)
        //        {
        //            bytes.add((( Record ) records.get(k)).serialize());
        //
        //        }
        //        for (int k = 0; k < bytes.size(); k++)
        //       {
        //            arraysize += (( byte [] ) bytes.get(k)).length;
        //        }
        for (int k = 0; k < records.size(); k++) {

            // byte[] rec = (( byte [] ) bytes.get(k));
            // System.arraycopy(rec, 0, data, offset + pos, rec.length);
            Record record = records.get(k);
            // Let's skip RECALCID records, as they are only use for optimization
            if(record.getSid() != RecalcIdRecord.sid || ((RecalcIdRecord)record).isNeeded()) {
		pos += record.serialize(pos + offset, data);   // rec.length;
            }
        }
        log.log(DEBUG, "Exiting serialize workbook");
        return pos;
    }

    public int getSize() {
        int retval = 0;

        for (int k = 0; k < records.size(); k++) {
            Record record = records.get(k);
            // Let's skip RECALCID records, as they are only use for optimization
            if(record.getSid() != RecalcIdRecord.sid || ((RecalcIdRecord)record).isNeeded()) {
		retval += record.getRecordSize();
            }
        }
        return retval;
    }

    /**
     * creates the BOF record
     * @see org.apache.poi.hssf.record.BOFRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a BOFRecord
     */

    protected Record createBOF() {
        BOFRecord retval = new BOFRecord();

        retval.setVersion(( short ) 0x600);
        retval.setType(( short ) 5);
        retval.setBuild(( short ) 0x10d3);

        //        retval.setBuild((short)0x0dbb);
        retval.setBuildYear(( short ) 1996);
        retval.setHistoryBitMask(0x41);   // was c1 before verify
        retval.setRequiredVersion(0x6);
        return retval;
    }

    /**
     * creates the InterfaceHdr record
     * @see org.apache.poi.hssf.record.InterfaceHdrRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a InterfaceHdrRecord
     */

    protected Record createInterfaceHdr() {
        InterfaceHdrRecord retval = new InterfaceHdrRecord();

        retval.setCodepage(CODEPAGE);
        return retval;
    }

    /**
     * creates an MMS record
     * @see org.apache.poi.hssf.record.MMSRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a MMSRecord
     */

    protected Record createMMS() {
        MMSRecord retval = new MMSRecord();

        retval.setAddMenuCount(( byte ) 0);
        retval.setDelMenuCount(( byte ) 0);
        return retval;
    }

    /**
     * creates the InterfaceEnd record
     * @see org.apache.poi.hssf.record.InterfaceEndRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a InterfaceEndRecord
     */

    protected Record createInterfaceEnd() {
        return new InterfaceEndRecord();
    }

    /**
     * creates the WriteAccess record containing the logged in user's name
     * @see org.apache.poi.hssf.record.WriteAccessRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a WriteAccessRecord
     */

    protected Record createWriteAccess() {
        WriteAccessRecord retval = new WriteAccessRecord();

        retval.setUsername(System.getProperty("user.name"));
        return retval;
    }

    /**
     * creates the Codepage record containing the constant stored in CODEPAGE
     * @see org.apache.poi.hssf.record.CodepageRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a CodepageRecord
     */

    protected Record createCodepage() {
        CodepageRecord retval = new CodepageRecord();

        retval.setCodepage(CODEPAGE);
        return retval;
    }

    /**
     * creates the DSF record containing a 0 since HSSF can't even create Dual Stream Files
     * @see org.apache.poi.hssf.record.DSFRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a DSFRecord
     */

    protected Record createDSF() {
        DSFRecord retval = new DSFRecord();

        retval.setDsf(
        ( short ) 0);   // we don't even support double stream files
        return retval;
    }

    /**
     * creates the TabId record containing an array of 0,1,2.  This release of HSSF
     * always has the default three sheets, no less, no more.
     * @see org.apache.poi.hssf.record.TabIdRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a TabIdRecord
     */

    protected Record createTabId() {
        TabIdRecord retval     = new TabIdRecord();
        short[]     tabidarray = {
            0
        };

        retval.setTabIdArray(tabidarray);
        return retval;
    }

    /**
     * creates the FnGroupCount record containing the Magic number constant of 14.
     * @see org.apache.poi.hssf.record.FnGroupCountRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a FnGroupCountRecord
     */

    protected Record createFnGroupCount() {
        FnGroupCountRecord retval = new FnGroupCountRecord();

        retval.setCount(( short ) 14);
        return retval;
    }

    /**
     * creates the WindowProtect record with protect set to false.
     * @see org.apache.poi.hssf.record.WindowProtectRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a WindowProtectRecord
     */

    protected Record createWindowProtect() {
        WindowProtectRecord retval = new WindowProtectRecord();

        retval.setProtect(
        false);   // by default even when we support it we won't
        return retval;   // want it to be protected
    }

    /**
     * creates the Protect record with protect set to false.
     * @see org.apache.poi.hssf.record.ProtectRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a ProtectRecord
     */

    protected Record createProtect() {
        ProtectRecord retval = new ProtectRecord();

        retval.setProtect(
        false);   // by default even when we support it we won't
        return retval;   // want it to be protected
    }

    /**
     * creates the Password record with password set to 0.
     * @see org.apache.poi.hssf.record.PasswordRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a PasswordRecord
     */

    protected Record createPassword() {
        PasswordRecord retval = new PasswordRecord();

        retval.setPassword(( short ) 0);   // no password by default!
        return retval;
    }

    /**
     * creates the ProtectionRev4 record with protect set to false.
     * @see org.apache.poi.hssf.record.ProtectionRev4Record
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a ProtectionRev4Record
     */

    protected Record createProtectionRev4() {
        ProtectionRev4Record retval = new ProtectionRev4Record();

        retval.setProtect(false);
        return retval;
    }

    /**
     * creates the PasswordRev4 record with password set to 0.
     * @see org.apache.poi.hssf.record.PasswordRev4Record
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a PasswordRev4Record
     */

    protected Record createPasswordRev4() {
        PasswordRev4Record retval = new PasswordRev4Record();

        retval.setPassword(( short ) 0);   // no password by default!
        return retval;
    }

    /**
     * creates the WindowOne record with the following magic values: <P>
     * horizontal hold - 0x168 <P>
     * vertical hold   - 0x10e <P>
     * width           - 0x3a5c <P>
     * height          - 0x23be <P>
     * options         - 0x38 <P>
     * selected tab    - 0 <P>
     * displayed tab   - 0 <P>
     * num selected tab- 0 <P>
     * tab width ratio - 0x258 <P>
     * @see org.apache.poi.hssf.record.WindowOneRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a WindowOneRecord
     */

    protected Record createWindowOne() {
        WindowOneRecord retval = new WindowOneRecord();

        retval.setHorizontalHold(( short ) 0x168);
        retval.setVerticalHold(( short ) 0x10e);
        retval.setWidth(( short ) 0x3a5c);
        retval.setHeight(( short ) 0x23be);
        retval.setOptions(( short ) 0x38);
        retval.setSelectedTab(( short ) 0x0);
        retval.setDisplayedTab(( short ) 0x0);
        retval.setNumSelectedTabs(( short ) 1);
        retval.setTabWidthRatio(( short ) 0x258);
        return retval;
    }

    /**
     * creates the Backup record with backup set to 0. (loose the data, who cares)
     * @see org.apache.poi.hssf.record.BackupRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a BackupRecord
     */

    protected Record createBackup() {
        BackupRecord retval = new BackupRecord();

        retval.setBackup(
        ( short ) 0);   // by default DONT save backups of files...just loose data
        return retval;
    }

    /**
     * creates the HideObj record with hide object set to 0. (don't hide)
     * @see org.apache.poi.hssf.record.HideObjRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a HideObjRecord
     */

    protected Record createHideObj() {
        HideObjRecord retval = new HideObjRecord();

        retval.setHideObj(( short ) 0);   // by default set hide object off
        return retval;
    }

    /**
     * creates the DateWindow1904 record with windowing set to 0. (don't window)
     * @see org.apache.poi.hssf.record.DateWindow1904Record
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a DateWindow1904Record
     */

    protected Record createDateWindow1904() {
        DateWindow1904Record retval = new DateWindow1904Record();

        retval.setWindowing(
        ( short ) 0);   // don't EVER use 1904 date windowing...tick tock..
        return retval;
    }

    /**
     * creates the Precision record with precision set to true. (full precision)
     * @see org.apache.poi.hssf.record.PrecisionRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a PrecisionRecord
     */

    protected Record createPrecision() {
        PrecisionRecord retval = new PrecisionRecord();

        retval.setFullPrecision(
        true);   // always use real numbers in calculations!
        return retval;
    }

    /**
     * creates the RefreshAll record with refreshAll set to true. (refresh all calcs)
     * @see org.apache.poi.hssf.record.RefreshAllRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a RefreshAllRecord
     */

    protected Record createRefreshAll() {
        RefreshAllRecord retval = new RefreshAllRecord();

        retval.setRefreshAll(false);
        return retval;
    }

    /**
     * creates the BookBool record with saveLinkValues set to 0. (don't save link values)
     * @see org.apache.poi.hssf.record.BookBoolRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a BookBoolRecord
     */

    protected Record createBookBool() {
        BookBoolRecord retval = new BookBoolRecord();

        retval.setSaveLinkValues(( short ) 0);
        return retval;
    }

    /**
     * creates a Font record with the following magic values: <P>
     * fontheight           = 0xc8<P>
     * attributes           = 0x0<P>
     * color palette index  = 0x7fff<P>
     * bold weight          = 0x190<P>
     * Font Name Length     = 5 <P>
     * Font Name            = Arial <P>
     *
     * @see org.apache.poi.hssf.record.FontRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a FontRecord
     */

    protected Record createFont() {
        FontRecord retval = new FontRecord();

        retval.setFontHeight(( short ) 0xc8);
        retval.setAttributes(( short ) 0x0);
        retval.setColorPaletteIndex(( short ) 0x7fff);
        retval.setBoldWeight(( short ) 0x190);
        retval.setFontNameLength(( byte ) 5);
        retval.setFontName("Arial");
        return retval;
    }

    /**
     * Creates a FormatRecord object
     * @param id    the number of the format record to create (meaning its position in
     *        a file as M$ Excel would create it.)
     * @return record containing a FormatRecord
     * @see org.apache.poi.hssf.record.FormatRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createFormat(int id) {   // we'll need multiple editions for
        FormatRecord retval = new FormatRecord();   // the differnt formats

        switch (id) {

            case 0 :
                retval.setIndexCode(( short ) 5);
                retval.setFormatStringLength(( byte ) 0x17);
                retval.setFormatString("\"$\"#,##0_);\\(\"$\"#,##0\\)");
                break;

            case 1 :
                retval.setIndexCode(( short ) 6);
                retval.setFormatStringLength(( byte ) 0x1c);
                retval.setFormatString("\"$\"#,##0_);[Red]\\(\"$\"#,##0\\)");
                break;

            case 2 :
                retval.setIndexCode(( short ) 7);
                retval.setFormatStringLength(( byte ) 0x1d);
                retval.setFormatString("\"$\"#,##0.00_);\\(\"$\"#,##0.00\\)");
                break;

            case 3 :
                retval.setIndexCode(( short ) 8);
                retval.setFormatStringLength(( byte ) 0x22);
                retval.setFormatString(
                "\"$\"#,##0.00_);[Red]\\(\"$\"#,##0.00\\)");
                break;

            case 4 :
                retval.setIndexCode(( short ) 0x2a);
                retval.setFormatStringLength(( byte ) 0x32);
                retval.setFormatString(
                "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)");
                break;

            case 5 :
                retval.setIndexCode(( short ) 0x29);
                retval.setFormatStringLength(( byte ) 0x29);
                retval.setFormatString(
                "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)");
                break;

            case 6 :
                retval.setIndexCode(( short ) 0x2c);
                retval.setFormatStringLength(( byte ) 0x3a);
                retval.setFormatString(
                "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)");
                break;

            case 7 :
                retval.setIndexCode(( short ) 0x2b);
                retval.setFormatStringLength(( byte ) 0x31);
                retval.setFormatString(
                "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)");
                break;
        }
        return retval;
    }

    /**
     * Creates an ExtendedFormatRecord object
     * @param id    the number of the extended format record to create (meaning its position in
     *        a file as MS Excel would create it.)
     *
     * @return record containing an ExtendedFormatRecord
     * @see org.apache.poi.hssf.record.ExtendedFormatRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createExtendedFormat(int id) {   // we'll need multiple editions
        ExtendedFormatRecord retval = new ExtendedFormatRecord();

        switch (id) {

            case 0 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 1 :
                retval.setFontIndex(( short ) 1);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 2 :
                retval.setFontIndex(( short ) 1);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 3 :
                retval.setFontIndex(( short ) 2);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 4 :
                retval.setFontIndex(( short ) 2);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 5 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 6 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 7 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 8 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 9 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 10 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 11 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 12 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 13 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 14 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff400);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

                // cell records
            case 15 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0);
                retval.setCellOptions(( short ) 0x1);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0x0);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

                // style
            case 16 :
                retval.setFontIndex(( short ) 1);
                retval.setFormatIndex(( short ) 0x2b);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff800);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 17 :
                retval.setFontIndex(( short ) 1);
                retval.setFormatIndex(( short ) 0x29);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff800);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 18 :
                retval.setFontIndex(( short ) 1);
                retval.setFormatIndex(( short ) 0x2c);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff800);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 19 :
                retval.setFontIndex(( short ) 1);
                retval.setFormatIndex(( short ) 0x2a);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff800);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 20 :
                retval.setFontIndex(( short ) 1);
                retval.setFormatIndex(( short ) 0x9);
                retval.setCellOptions(( short ) 0xfffffff5);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0xfffff800);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

                // unused from this point down
            case 21 :
                retval.setFontIndex(( short ) 5);
                retval.setFormatIndex(( short ) 0x0);
                retval.setCellOptions(( short ) 0x1);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0x800);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 22 :
                retval.setFontIndex(( short ) 6);
                retval.setFormatIndex(( short ) 0x0);
                retval.setCellOptions(( short ) 0x1);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0x5c00);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 23 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0x31);
                retval.setCellOptions(( short ) 0x1);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0x5c00);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 24 :
                retval.setFontIndex(( short ) 0);
                retval.setFormatIndex(( short ) 0x8);
                retval.setCellOptions(( short ) 0x1);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0x5c00);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;

            case 25 :
                retval.setFontIndex(( short ) 6);
                retval.setFormatIndex(( short ) 0x8);
                retval.setCellOptions(( short ) 0x1);
                retval.setAlignmentOptions(( short ) 0x20);
                retval.setIndentionOptions(( short ) 0x5c00);
                retval.setBorderOptions(( short ) 0);
                retval.setPaletteOptions(( short ) 0);
                retval.setAdtlPaletteOptions(( short ) 0);
                retval.setFillPaletteOptions(( short ) 0x20c0);
                break;
        }
        return retval;
    }

    /**
     * creates an default cell type ExtendedFormatRecord object.
     * @return ExtendedFormatRecord with intial defaults (cell-type)
     */

    protected ExtendedFormatRecord createExtendedFormat() {
        ExtendedFormatRecord retval = new ExtendedFormatRecord();

        retval.setFontIndex(( short ) 0);
        retval.setFormatIndex(( short ) 0x0);
        retval.setCellOptions(( short ) 0x1);
        retval.setAlignmentOptions(( short ) 0x20);
        retval.setIndentionOptions(( short ) 0);
        retval.setBorderOptions(( short ) 0);
        retval.setPaletteOptions(( short ) 0);
        retval.setAdtlPaletteOptions(( short ) 0);
        retval.setFillPaletteOptions(( short ) 0x20c0);
        retval.setTopBorderPaletteIdx(HSSFColor.BLACK.index);
        retval.setBottomBorderPaletteIdx(HSSFColor.BLACK.index);
        retval.setLeftBorderPaletteIdx(HSSFColor.BLACK.index);
        retval.setRightBorderPaletteIdx(HSSFColor.BLACK.index);
        return retval;
    }

    /**
     * Creates a StyleRecord object
     * @param id        the number of the style record to create (meaning its position in
     *                  a file as MS Excel would create it.
     * @return record containing a StyleRecord
     * @see org.apache.poi.hssf.record.StyleRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createStyle(int id) {   // we'll need multiple editions
        StyleRecord retval = new StyleRecord();

        switch (id) {

            case 0 :
                retval.setIndex(( short ) 0xffff8010);
                retval.setBuiltin(( byte ) 3);
                retval.setOutlineStyleLevel(( byte ) 0xffffffff);
                break;

            case 1 :
                retval.setIndex(( short ) 0xffff8011);
                retval.setBuiltin(( byte ) 6);
                retval.setOutlineStyleLevel(( byte ) 0xffffffff);
                break;

            case 2 :
                retval.setIndex(( short ) 0xffff8012);
                retval.setBuiltin(( byte ) 4);
                retval.setOutlineStyleLevel(( byte ) 0xffffffff);
                break;

            case 3 :
                retval.setIndex(( short ) 0xffff8013);
                retval.setBuiltin(( byte ) 7);
                retval.setOutlineStyleLevel(( byte ) 0xffffffff);
                break;

            case 4 :
                retval.setIndex(( short ) 0xffff8000);
                retval.setBuiltin(( byte ) 0);
                retval.setOutlineStyleLevel(( byte ) 0xffffffff);
                break;

            case 5 :
                retval.setIndex(( short ) 0xffff8014);
                retval.setBuiltin(( byte ) 5);
                retval.setOutlineStyleLevel(( byte ) 0xffffffff);
                break;
        }
        return retval;
    }

    /**
     * Creates a palette record initialized to the default palette
     * @return a PaletteRecord instance populated with the default colors
     * @see org.apache.poi.hssf.record.PaletteRecord
     */
    protected PaletteRecord createPalette()
    {
        return new PaletteRecord(PaletteRecord.sid);
    }
    
    /**
     * Creates the UseSelFS object with the use natural language flag set to 0 (false)
     * @return record containing a UseSelFSRecord
     * @see org.apache.poi.hssf.record.UseSelFSRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createUseSelFS() {
        UseSelFSRecord retval = new UseSelFSRecord();

        retval.setFlag(( short ) 0);
        return retval;
    }

    /**
     * create a "bound sheet" or "bundlesheet" (depending who you ask) record
     * Always sets the sheet's bof to 0.  You'll need to set that yourself.
     * @param id either sheet 0,1 or 2.
     * @return record containing a BoundSheetRecord
     * @see org.apache.poi.hssf.record.BoundSheetRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createBoundSheet(int id) {   // 1,2,3 sheets
        BoundSheetRecord retval = new BoundSheetRecord();

        switch (id) {

            case 0 :
                retval.setPositionOfBof(0x0);   // should be set later
                retval.setOptionFlags(( short ) 0);
                retval.setSheetnameLength(( byte ) 0x6);
                retval.setCompressedUnicodeFlag(( byte ) 0);
                retval.setSheetname("Sheet1");
                break;

            case 1 :
                retval.setPositionOfBof(0x0);   // should be set later
                retval.setOptionFlags(( short ) 0);
                retval.setSheetnameLength(( byte ) 0x6);
                retval.setCompressedUnicodeFlag(( byte ) 0);
                retval.setSheetname("Sheet2");
                break;

            case 2 :
                retval.setPositionOfBof(0x0);   // should be set later
                retval.setOptionFlags(( short ) 0);
                retval.setSheetnameLength(( byte ) 0x6);
                retval.setCompressedUnicodeFlag(( byte ) 0);
                retval.setSheetname("Sheet3");
                break;
        }
        return retval;
    }

    /**
     * Creates the Country record with the default country set to 1
     * and current country set to 7 in case of russian locale ("ru_RU") and 1 otherwise
     * @return record containing a CountryRecord
     * @see org.apache.poi.hssf.record.CountryRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createCountry() {   // what a novel idea, create your own!
        CountryRecord retval = new CountryRecord();

        retval.setDefaultCountry(( short ) 1);

        // from Russia with love ;)
        if ( Locale.getDefault().toString().equals( "ru_RU" ) ) {
	        retval.setCurrentCountry(( short ) 7);
        }
        else {
	        retval.setCurrentCountry(( short ) 1);
        }

        return retval;
    }

    /**
     * Creates the SST record with no strings and the unique/num string set to 0
     * @return record containing a SSTRecord
     * @see org.apache.poi.hssf.record.SSTRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createSST() {
        return new SSTRecord();
    }

    /**
     * Creates the ExtendedSST record with numstrings per bucket set to 0x8.  HSSF
     * doesn't yet know what to do with this thing, but we create it with nothing in
     * it hardly just to make Excel happy and our sheets look like Excel's
     *
     * @return record containing an ExtSSTRecord
     * @see org.apache.poi.hssf.record.ExtSSTRecord
     * @see org.apache.poi.hssf.record.Record
     */

    protected Record createExtendedSST() {
        ExtSSTRecord retval = new ExtSSTRecord();

        retval.setNumStringsPerBucket(( short ) 0x8);
        return retval;
    }

    /**
     * creates the EOF record
     * @see org.apache.poi.hssf.record.EOFRecord
     * @see org.apache.poi.hssf.record.Record
     * @return record containing a EOFRecord
     */

    protected Record createEOF() {
        return new EOFRecord();
    }

    public SheetReferences getSheetReferences() {
       SheetReferences refs = new SheetReferences();

       if (externSheet != null) {
          for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) {
              String sheetName = findSheetNameFromExternSheet((short)k);
              refs.addSheetReference(sheetName, k);
          }
       }
       return refs;
    }

    /** finds the sheet name by his extern sheet index
     * @param num extern sheet index
     * @return sheet name
     */
    public String findSheetNameFromExternSheet(short num){
        String result;

        short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook();
        result = getSheetName(indexToSheet);

        return result;
    }

    /**
     * Finds the sheet index for a particular external sheet number.
     * @param externSheetNumber     The external sheet number to convert
     * @return  The index to the sheet found.
     */
    public int getSheetIndexFromExternSheetIndex(int externSheetNumber)
    {
        if (externSheetNumber >= externSheet.getNumOfREFStructures())
            return -1;
        else
            return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
    }

    /** returns the extern sheet number for specific sheet number ,
     *  if this sheet doesn't exist in extern sheet , add it
     * @param sheetNumber sheet number
     * @return index to extern sheet
     */
    public short checkExternSheet(int sheetNumber){

        int i = 0;
        boolean flag = false;
        short result = 0;

        if (externSheet == null) {
            externSheet = createExternSheet();
        }

        //Trying to find reference to this sheet
        while (i < externSheet.getNumOfREFStructures() && !flag){
            ExternSheetSubRecord record = externSheet.getREFRecordAt(i);

            if (record.getIndexToFirstSupBook() ==  sheetNumber &&
            record.getIndexToLastSupBook() == sheetNumber){
                flag = true;
                result = (short) i;
            }

            ++i;
        }

        //We Havent found reference to this sheet
        if (!flag) {
            result = addSheetIndexToExternSheet((short) sheetNumber);
        }

        return result;
    }

    private short addSheetIndexToExternSheet(short sheetNumber){
        short result;

        ExternSheetSubRecord record = new ExternSheetSubRecord();
        record.setIndexToFirstSupBook(sheetNumber);
        record.setIndexToLastSupBook(sheetNumber);
        externSheet.addREFRecord(record);
        externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1));
        result = (short)(externSheet.getNumOfREFStructures() - 1);

        return result;
    }



    /** gets the total number of names
     * @return number of names
     */
    public int getNumNames(){
        int result = names.size();

        return result;
    }

    /** gets the name record
     * @param index name index
     * @return name record
     */
    public NameRecord getNameRecord(int index){
        NameRecord result = (NameRecord) names.get(index);

        return result;

    }

    /** creates new name
     * @return new name record
     */
    public NameRecord createName(){

        NameRecord name = new NameRecord();

        // Not the most efficient way but the other way was causing too many bugs
        int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
        if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
        if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);

        records.add(idx+names.size()+1, name);
        names.add(name);

        return name;
    }


    /** creates new name
     * @return new name record
     */
    public NameRecord addName(NameRecord name)
    {
        // Not the most efficient way but the other way was causing too many bugs
        int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
        if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
        if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
        records.add(idx+names.size()+1, name);
        names.add(name);

        return name;
    }

	/**Generates a NameRecord to represent a built-in region
	 * @return a new NameRecord unless the index is invalid
	 */
	public NameRecord createBuiltInName(byte builtInName, int index)
	{
		if (index == -1 || index+1 > (int)Short.MAX_VALUE) 
			throw new IllegalArgumentException("Index is not valid ["+index+"]");
	    
		NameRecord name = new NameRecord(builtInName, (short)(index));
	            
		addName(name);
	    
		return name;
	}


    /** removes the name
     * @param namenum name index
     */
    public void removeName(int namenum){
        if (names.size() > namenum) {
            int idx = findFirstRecordLocBySid(NameRecord.sid);
            records.remove(idx + namenum);
            names.remove(namenum);
        }

    }

    /** creates a new extern sheet record
     * @return the new extern sheet record
     */
    protected ExternSheetRecord createExternSheet(){
        ExternSheetRecord externSheet = new ExternSheetRecord();

        int idx = findFirstRecordLocBySid(CountryRecord.sid);

        records.add(idx+1, externSheet);
//        records.add(records.supbookpos + 1 , rec);

        //We also adds the supBook for internal reference
        SupBookRecord supbook = new SupBookRecord();

        supbook.setNumberOfSheets((short)getNumSheets());
        //supbook.setFlag();

        records.add(idx+1, supbook);
//        records.add(records.supbookpos + 1 , supbook);

        return externSheet;
    }

    /**
     * Returns a format index that matches the passed in format.  It does not tie into HSSFDataFormat.
     * @param format the format string
     * @param createIfNotFound creates a new format if format not found
     * @return the format id of a format that matches or -1 if none found and createIfNotFound
     */
    public short getFormat(String format, boolean createIfNotFound) {
	Iterator iterator;
	for (iterator = formats.iterator(); iterator.hasNext();) {
	    FormatRecord r = (FormatRecord)iterator.next();
	    if (r.getFormatString().equals(format)) {
		return r.getIndexCode();
	    }
	}

	if (createIfNotFound) {
	    return createFormat(format);
	}

	return -1;
    }

    /**
     * Returns the list of FormatRecords in the workbook.
     * @return ArrayList of FormatRecords in the notebook
     */
    public ArrayList getFormats() {
	return formats;
    }

    /**
     * Creates a FormatRecord, inserts it, and returns the index code.
     * @param format the format string
     * @return the index code of the format record.
     * @see org.apache.poi.hssf.record.FormatRecord
     * @see org.apache.poi.hssf.record.Record
     */
    public short createFormat( String format )
    {
//        ++xfpos;	//These are to ensure that positions are updated properly
//        ++palettepos;
//        ++bspos;
        FormatRecord rec = new FormatRecord();
        maxformatid = maxformatid >= (short) 0xa4 ? (short) ( maxformatid + 1 ) : (short) 0xa4; //Starting value from M$ empiracle study.
        rec.setIndexCode( maxformatid );
        rec.setFormatStringLength( (byte) format.length() );
        rec.setFormatString( format );

        int pos = 0;
        while ( pos < records.size() && records.get( pos ).getSid() != FormatRecord.sid )
            pos++;
        pos += formats.size();
        formats.add( rec );
        records.add( pos, rec );
        return maxformatid;
    }


    /**
     * Returns the first occurance of a record matching a particular sid.
     */
    public Record findFirstRecordBySid(short sid) {
        for (Iterator iterator = records.iterator(); iterator.hasNext(); ) {
            Record record = ( Record ) iterator.next();
            
            if (record.getSid() == sid) {
                return record;
            }
        }
        return null;
    }

    /**
     * Returns the index of a record matching a particular sid.
     * @param sid   The sid of the record to match
     * @return      The index of -1 if no match made.
     */
    public int findFirstRecordLocBySid(short sid) {
        int index = 0;
        for (Iterator iterator = records.iterator(); iterator.hasNext(); ) {
            Record record = ( Record ) iterator.next();

            if (record.getSid() == sid) {
                return index;
            }
            index ++;
        }
        return -1;
    }

    /**
     * Returns the next occurance of a record matching a particular sid.
     */
    public Record findNextRecordBySid(short sid, int pos) {
        int matches = 0;
        for (Iterator iterator = records.iterator(); iterator.hasNext(); ) {
            Record record = ( Record ) iterator.next();

            if (record.getSid() == sid) {
                if (matches++ == pos)
                    return record;
            }
        }
        return null;
    }

    public List getRecords()
    {
        return records.getRecords();
    }

//    public void insertChartRecords( List chartRecords )
//    {
//        backuppos += chartRecords.size();
//        fontpos += chartRecords.size();
//        palettepos += chartRecords.size();
//        bspos += chartRecords.size();
//        xfpos += chartRecords.size();
//
//        records.addAll(protpos, chartRecords);
//    }

    /**
    * Whether date windowing is based on 1/2/1904 or 1/1/1900.
    * Some versions of Excel (Mac) can save workbooks using 1904 date windowing.
    *
    * @return true if using 1904 date windowing
    */
    public boolean isUsing1904DateWindowing() {
        return uses1904datewindowing;
    }
    
    /**
     * Returns the custom palette in use for this workbook; if a custom palette record
     * does not exist, then it is created.
     */
    public PaletteRecord getCustomPalette()
    {
        PaletteRecord palette;
        Record rec = records.get(records.getPalettepos());
        if (rec instanceof PaletteRecord)
        {
            palette = (PaletteRecord) rec;
        }
        else
        {
            palette = createPalette();
            records.add(records.getPalettepos(), palette);
        }
        return palette;
    }
 
    
}

